/*@parser.c::*/
/** 
 * Simplified Pascal in the EBNF:
 * 
 * program -> PROGRAM ID ';' block '.'
 * 
 * block -> declarations modules stmtblock
 * 
 * declarations -> 
 *    [CONST ID = constant';' {ID = constant';'}]
 *    [TYPE ID = type';' {ID = type';'}]
 *    [VAR idlist ':' type';' {idlist ':' type';'}]
 * 
 * modules -> {procedure | function}
 * 
 * procedure -> PROCEDURE ID formalparam';' block ';'
 * 
 * function -> FUNCTION ID formalparam ':' typeid ';'block';'
 * 
 * stmtblock -> BEGIN stmtlist END
 * 
 * stmtlist -> stmt {';' stmt}
 * 
 * stmt -> stmtblock | ifstmt | whilestmt | repstmt | forstmt
 *      | gotostmt | casestmt | idstmt
 */
#include <parser.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <keywords.h>
#include <tokens.h>
#include <symtab.h>
#include <lexer.h>
#include <errcode.h>
#include <codegen.h>

#define MAXIDLIST 128

int giVarPos[MAXIDLIST];
int giVarCount = 0;
int giLabelCnt = 1;
int giLexCat;
int giLexType;
int giExprType;
int giLexLevel;
int giLeftAttrType = 0; 
int giErrFlag = NOERR; // 0x0
int giNextErrEntry = 0;
int giParamPos[MAXIDLIST];
int giParamEntry = 0;
char szAsmBuffer[256];
char szIdList[32][MAXIDLIST];
char szCmpOp[8];
char szErrMsg[MAXERRENTRY][MAXERRLEN];
extern int iSymtab[MAXSYMTABENTRIES][MAXSYMTABCOLUMNS];
/* program -> PROGRAM ID ';' block '.'*/
void program(void)
{
  match(PROGRAM); 
  match(ID);
  match(';');
  block();
  match('.');
  /** */{asm_End();}/** */
}

/* block -> declarations stmtblock*/
void block(void)
{
  declarations();
  modules();
  {/** */
    asm_Section("globl main");
    asm_WriteTab(".type main, @function");
    asm_Label("main");
  }/** */
  stmtblock();
}

/* declarations -> [VAR idlist ':' type';' {idlist ':' type';'}]
 *                 [CONST ID = constant';' {ID = constant';'}]
 */
void declarations(void)
{
  int i;
  
  memset(&szAsmBuffer, 0, sizeof(szAsmBuffer));
  {/** if there are any global declarations we must use .section directive*/
    if( (lookahead == CONST || lookahead == VAR) && giLexLevel == 0 )
      asm_SectionNoNL("section");
  }/** */
  /*  Constant declaration is here */
  if( lookahead == CONST ){
    match(CONST);
    {/** */
      if ( giLexLevel == 0 ){ /* GLOBAL Lexical Level */
        memset(szAsmBuffer, 0, sizeof(szAsmBuffer));
        strcpy(szAsmBuffer, ".rodata\n");
      }
    }/** */
rep_const:
    {/** */
      if ( giLexLevel == 0 ){ /* GLOBAL Lexical Level */
        sprintf(szAsmBuffer, "%s:\n", lexeme); /* Const content must be defined inside constant() procedure */
      }
      giLexCat = CONST;
      iSymtab[symtab_insert(lexeme)][SYMLXTYPE] = CONST; /* One liners will love, haters will hate*/
    }/** */
    match(ID);
    match('=');
    constant();
    match(';');
    if( lookahead == ID ) goto rep_const;
  }
/* Variable declaration is here */
  if( lookahead == VAR ){
    match(VAR); 
    {/**  */
      giLexCat = VAR;
      if ( giLexLevel == 0 ){ /* GLOBAL Level */
        asm_Section("bss");
      }
    }/** */
rep_idlist:
    idlist();
    match(':');
    {/** */ 
      for ( i=0; i<giVarCount; i++ ){
	iSymtab[giVarPos[i]][SYMLXTYPE] = lookahead;
	if ( giLexLevel == 0 ) /* GLOBAL Level */
	  asm_Declaration(szIdList[i], lookahead);
      }
    }/** */
    type();
    match(';');
    if( lookahead == ID )
      goto rep_idlist;
  }
  if ( szAsmBuffer[0] )
    asm_Write(szAsmBuffer);
}
/* constant -> ['+'|'-'] (ID|UINT) */
void constant(void){
  if ( lookahead == '+' || lookahead == '-' )
    match(lookahead);
  
  if ( lookahead == ID )
    match(ID);
  else 
    match(UINT);
  
}
/* modules -> {procedure | function} */
void modules(void)
{
  asm_Section("text");
  while( lookahead == PROCEDURE || lookahead == FUNCTION ){
    if( lookahead == PROCEDURE ) procedure();
    else function();
  }
}
/* procedure ->  PROCEDURE ID formalparam';' block ';' */
void procedure(void)
{
  int iMyPos;
  match(PROCEDURE);
  {/** */
    giLexCat = PROCEDURE;
    iMyPos = symtab_insert(lexeme);
    asm_Label(lexeme);
    asm_PushReg("%ebp");
    asm_Mov("%esp","%ebp");
    giLexLevel++;
  }/** */
  match(ID);
  formalparam(iMyPos);
  {/** */
    iSymtab[iMyPos][SYMPARMCNT] = giParamEntry; /* Defining data type for this function */  
  }/** */
  match(';');
  declarations();
  stmtblock();
  {/** */
    symtab_dispose();
    giLexLevel--;
  }/** */
  match(';');
  asm_Ret();
}
/* function -> FUNCTION ID formalparam ':' type ';'block';' */
void function(void)
{
  int iMyPos;
  match(FUNCTION);
  {/** */
    giLexCat = FUNCTION;
    iMyPos = symtab_insert(lexeme);
    asm_Label(lexeme);
    asm_PushReg("%ebp");
    asm_Mov("%esp","%ebp");
    giLexLevel++;
  }/** */
  match(ID);
  formalparam(iMyPos);
  match(':');
  {/** */
    iSymtab[iMyPos][SYMPARMCNT] = giParamEntry; /* Param Count */ 
    iSymtab[iMyPos][SYMLXTYPE] = lookahead; /* Defining data type for this function */
  }/** */
  type();
  match(';');
  declarations();
  stmtblock();  
  {/** */
    symtab_dispose();
    giLexLevel--;
  }/** */
  match(';');
  asm_Ret();
}

/* idlist -> ID {','ID} */
void idlist(void)
{      
  char *p;
  {/** */
    giVarCount = 0;
    memset(&szIdList, 0, sizeof(szIdList));
    for( p=lexeme; *p; p++ ) *p = tolower(*p);
    strcpy(szIdList[giVarCount],lexeme);
    giVarPos[giVarCount++] = symtab_insert(lexeme);
  }/** */
  match(ID);
  while(lookahead==','){
    match(',');
    {/**  */
      for( p=lexeme; *p; p++ ) *p = tolower(*p);
      strcpy(szIdList[giVarCount],lexeme);
      giVarPos[giVarCount++] = symtab_insert(lexeme);    
    }/** */
    match(ID);
  }
  {/** Setting variable offset(just in case) */
    if ( giLexLevel == 1 ){ /* Inside a procedure ou function? */
      int i, iEspOffset = 0;
      char szTmp[8];
      for ( i=0; i < giVarCount; i++ ){
	iSymtab[giVarPos[i]][SYMOFFSET] = -4*(i+1); /* Offset is negative, cuz our stack grow inversly on memory */
        iEspOffset += 4;
      }
      if ( giLexCat == VAR ){ /* Allocate size to internal vars? */
        sprintf(szTmp, "$%d", iEspOffset);
        asm_Subl(szTmp, "%esp");
      }
    }
  }/** */
}
/* type -> INTEGER | REAL | BOOLEAN */
void type(void)
{
  switch(lookahead){
    case INTEGER:
      match(INTEGER);
      break;
    case BOOL:
      match(BOOL);
      break;
    default:
      match(REAL);
  }
}
/* stmt -> stmtblock | ifstmt | whilestmt | repstmt | idstmt | "" */
void stmt(void)
{
  switch(lookahead){
    case BEGIN:
      stmtblock();
      break;
    case IF:
      ifstmt();
      break;
    case WHILE:
      whilestmt();
      break;
    case REPEAT:
      repstmt();
      break;
    case ID:
      idstmt();
      break;
  }
}
/* stmtblock -> BEGIN stmtlist END */
void stmtblock(void)
{
  match(BEGIN);stmtlist();match(END);
}

/* stmtblock -> stmt { ';' stmt }*/
void stmtlist(void)
{
  stmt();
  while(lookahead==';'){
    match(';');
    stmt();
  }
}
/* ifstmt -> IF expression THEN stmt [ELSE stmt] */
void ifstmt(void)
{
  int iLabel;
  char szLabel[8];
  match(IF);
  expression();
  {/** */
    sprintf(szLabel, "%d", iLabel = giLabelCnt++);
    asm_Cmp_Jmp(szLabel);
  }/** */
  match(THEN);
  stmt();
  if(lookahead==ELSE) {
    match(ELSE);	
    {/** */
          sprintf(szLabel, "%d", giLabelCnt);
	  asm_Jump(szLabel);
          sprintf(szLabel, ".L%d", iLabel);
	  asm_Label(szLabel);
    }/** */
    stmt();
  }	
  {/** */
	sprintf(szLabel, ".L%d", giLabelCnt++);
	asm_Label(szLabel);
  }/** */
}
/* whilestmt -> WHILE expression DO stmt */
void whilestmt(void)
{
  int iLabelBegin, iLabelEnd;
  char szWrk[8];
  match(WHILE);
  {
    sprintf(szWrk, ".L%d", iLabelBegin = giLabelCnt++);
    asm_Label(szWrk);
  }
  expression();
  {
    sprintf(szWrk, "%d", iLabelEnd = giLabelCnt++);
    asm_Cmp_Jmp(szWrk);
  }
  match(DO);
  stmt();
  {
    sprintf(szWrk, "%d", iLabelBegin);
    asm_Jump(szWrk);
    sprintf(szWrk, ".L%d", iLabelEnd);
    asm_Label(szWrk);
  }
}
/* repstmt -> REPEAT stmtlist UNTIL expression */
void repstmt(void)
{
  int iLabelBegin, iLabelEnd;
  char szWrk[8];
  match(REPEAT);
  {
    sprintf(szWrk, ".L%d", iLabelBegin = giLabelCnt++);
    asm_Label(szWrk);
  }
  stmtlist();
  match(UNTIL);
  expression();
  {
    sprintf(szWrk, "%d", iLabelEnd = giLabelCnt++);
    asm_Cmp_Jmp(szWrk);
    sprintf(szWrk, "%d", iLabelBegin);
    asm_Jump(szWrk);
    sprintf(szWrk, ".L%d", iLabelEnd);
    asm_Label(szWrk);
  }
}
/* idstmt -> assgnmt | proccall */
void idstmt(void)
{
  char szLexeme[MAXIDLIST];
  char *p;
  {/** */
    if ( !(giVarPos[0] = symtab_lookup(lexeme)) ){
       fprintf(stderr, "undefined symbol %s\n", lexeme);
       exit(-1);
    }
    strcpy(szLexeme, lexeme);
    for( p=szLexeme; *p; p++ ) *p = tolower(*p);
  }/** */
  match(ID);
  if( lookahead == ASSGNMT || lookahead == '[' || lookahead == '^' )
     assgnmt(szLexeme);
  else{
     proccall(szLexeme);
  }
}
/* assgnmt -> (variable | ID ) ':=' expression */
void assgnmt(char *pszLexeme)
{
    int iMyPos = 9999;
    giLeftAttrType = iSymtab[giVarPos[0]][SYMLXTYPE];
    if ( iSymtab[giVarPos[0]][SYMLXCTG] == VAR || iSymtab[giVarPos[0]][SYMLXCTG] == PARAM ){
       iMyPos = giVarPos[0];
       variable();
    }
    match(ASSGNMT);
    expression();
    {/** */
      if ( giLexType != giLeftAttrType ){
	giErrFlag = giErrFlag | WARNINGERR;
	sprintf(szErrMsg[giNextErrEntry++], 
    "Line %d. Warning: Assignment makes %s to %s without a cast.\n", 
          linenumber,
	  pWichKeyword(giLeftAttrType),
	  pWichKeyword(giLexType)
	);
      }
      giLeftAttrType = 0;
      if ( iMyPos != 9999 ){
        if ( iSymtab[iMyPos][SYMLXLVL] == 0 ){/* Global */
	  asm_Mov("%eax", pszLexeme);
        }else
	  asm_Mov2Mem("%eax", "%ebp", iSymtab[iMyPos][SYMOFFSET]);
      }
    }/** */
}
/* proccall -> ID ['(' exprlist ')']  */
void proccall(char *pszLexeme){
    if ( lookahead == '(' ){
      match('(');
      exprlist(giVarPos[0]);
      asm_Call(pszLexeme);
      match(')');
    }
}
/* variable -> ID { '[' expr ']' | '^'  } */
void variable(void){
   while ( lookahead == '[' || lookahead == '^' ){
     if ( lookahead == '[' ){
       match('[');
       expr();
       match(']');
     }
     else
       match('^');
   }
}
int isrelationalop(token_t token)
{
  switch(token){
    case '=':
    case '>':
    case '<':
    case GEQ:
    case LEQ:
    case NEQ:
      return token;
  }
  return 0;
}
int isaddop(token_t token)
{
  switch(token){
    case '+':
    case '-':
    case OR:
      return token;
  }
  return 0;
}
int ismulop(token_t token)
{
  switch(token){
    case '*':
    case '/':
    case AND:
    case DIV:
    case MOD:
      return token;
  }
  return 0;
}
/* expression -> expr [RELOP expr]*/
void expression(void)
{
  /** an inherited attribute is get from the left side of an assgnmt */
  expr();
  {/** */
    if ( giLexCat != PARAM )
      giLexType = iAssertType(giLeftAttrType,giLexType);
  }/** */
  if(isrelationalop(lookahead)){
    {/** */
      strcpy(szCmpOp, lexeme);
      asm_PushReg("%eax");
      if ( giLexType == REAL ){ /* Relationship between REAL and BOOLEAN?? */
        giErrFlag = giErrFlag | FATALERR; /* Yes, Setting fatal error flag */
	sprintf(szErrMsg[giNextErrEntry++], 
      "Line %d. ERROR: Cannot use REAL on BOOLEAN relationship.\n", 
          linenumber
	);
      }
      giLexType = BOOL;
    }/** */
    match(lookahead); 
    expr();
    { /** Data type synthesized as an atributte*/
      if ( giLexCat != PARAM )
        giLexType = iAssertType(giLeftAttrType,giLexType);
    }
  }
}
/* expr -> ['-'] term {ADD term} */
void expr(void)
{
  char szOp[8];
  int iNegate = 0;
  if(lookahead=='-'){
    match('-');
    iNegate = 1;
  }
  term();
  if ( iNegate ) 
    asm_NegateReg("%eax");
  while( lookahead == ADD || lookahead == OR ){
    strcpy(szOp, lexeme);
    match(lookahead); 
    {/**Push eax to calculate the result */
      asm_PushReg("%eax");
    }/** */
    term();
    {/** Operates SUM. or SUBT. on the top of the stack */
      asm_ADDStack(szOp);
     /** result is on top of the stack, must be poped to eax*/
      asm_PopReg("%eax"); 
    }/** */
  }
}
/* term -> factor {MUL factor} */
void term(void)
{
  char szOp[8];
  factor();
  while( lookahead == MUL || lookahead == AND ){
    strcpy(szOp, lexeme);
    match(lookahead); 
    {/**Push eax to calculate the result */
      asm_PushReg("%eax");
    }/** */
    factor();
    {/** Operates MULT. or DIV. on the top of the stack */
      asm_MULStack(szOp);
     /** result is on top of the stack, must be poped to eax*/
      asm_PopReg("%eax"); 
    }/** */
  }
}
/*factor -> UINT | FLOAT | ID ['(' exprlist ')'] | '(' expression ')' | NOT factor */
void factor(void){
    char factLexeme[128], szTmp[32];
    float fValue;
    switch(lookahead){
      case UINT:
	asm_MovImm2Reg(lexeme, "%eax");
        match(UINT);
	giLexType = INTEGER;
	break;
      case FLOAT:
	fValue = strtod(lexeme,NULL);
	sprintf(szTmp, "0x%X", *(unsigned int*)&fValue);
	asm_MovImm2Reg(szTmp, "%eax");
	
        match(FLOAT);
	giLexType = REAL;
	break;      
      case BOOL:
        match(BOOL);
	giLexType = BOOL;
	break;
      case ID:
	{/** */
	  if ( !(giVarPos[0] = symtab_lookup(lexeme)) ){
	    fprintf(stderr, "undefined symbol %s\n", lexeme);
	    exit(-1);
	  }
	  if ( iSymtab[giVarPos[0]][SYMLXCTG] == PROCEDURE ) {
	    fprintf(stderr, "Cannot call procedure %s here\n", lexeme);
	    exit(-1);
	  }
	  strcpy(factLexeme, lexeme); // Saving symbol for future check
	  giLexType = iSymtab[giVarPos[0]][SYMLXTYPE];
	}/** */
        match(ID);
        if(lookahead=='('){
	  int iMyType;
          match('(');
	  iMyType = giLexType;
	  exprlist(giVarPos[0]);
	  giLexType = iMyType;
	  match(')');
	  {/** **/
	    asm_Call(factLexeme);
	  }/** **/
        }
        else{
	  if ( iSymtab[giVarPos[0]][SYMLXLVL] == 0 ){
	    /**  GLOBAL */
	    char *p;
	    for( p=factLexeme; *p; p++ ) *p = tolower(*p);
	    asm_Mov(factLexeme, "%eax");
	  }
	  else{
	    /**  LOCAL */
	    asm_MovFromMem("%ebp", "%eax", iSymtab[giVarPos[0]][SYMOFFSET]);
	  }
	}
        break;
      case '(':
        {/** */giExprType = 0;/** */}
        match('(');expression();match(')');
	giLexType = iAssertType(giLexType, giExprType);
	break;
      default:
        match(NOT);
	factor();
	{/** */
	  asm_PopReg("%eax");
	  asm_NegateReg("%eax");
	  asm_PushReg("%eax");
	}/** */
    }
}
/*exprlist -> expression {',' expression}*/
void exprlist(int iProcFuncPos)
{
  int iParamTypeSeq = SYMPARMCNT + 1;
  int iPrmCnt = 1;
  int iMyCat;
  {/** */
    iMyCat = giLexCat;
    giLexCat = PARAM;
  }/** */
  expression();

  {/** Param Type sanity check  */
    if ( giLexType != iSymtab[iProcFuncPos][iParamTypeSeq++] ){
      giErrFlag = giErrFlag | WARNINGERR;
      sprintf(szErrMsg[giNextErrEntry++], 
   "Line %d. Warning: Parameter %d of %s is %s, you entered %s.\n", 
	linenumber,
	iPrmCnt++,
	&szLexTape[iSymtab[iProcFuncPos][SYMLXIDX]],
	pWichKeyword(iSymtab[iProcFuncPos][iParamTypeSeq-1]),
	pWichKeyword(giLexType)
      );
    }
   /** expression result is stored in register eax
    * push the register onto stack */
    asm_PushReg("%eax");
  }/** */
  while(lookahead==','){
    match(',');expression();
     /** expression result is stored in register eax
      * push the register onto stack */
    {/** */
      /** Param Type sanity check */
      if ( giLexType != iSymtab[iProcFuncPos][iParamTypeSeq++] ){
	giErrFlag = giErrFlag | WARNINGERR;
	sprintf(szErrMsg[giNextErrEntry++], 
    "Line %d. Warning: Parameter %d of %s is %s, you entered %s.\n", 
	  linenumber,
	  iPrmCnt++,
	  &szLexTape[iSymtab[iProcFuncPos][SYMLXIDX]],
	  pWichKeyword(iSymtab[iProcFuncPos][iParamTypeSeq-1]),
	  pWichKeyword(giLexType)
	);
      }
      asm_PushReg("%eax");
    }/** */
  }
  giLexCat = iMyCat;
}
/*formalparam -> '('[VAR] varlist ':' typespec ';' { varlist ':' typespec ';' } ')' */
void formalparam(int iProcFuncPos){
  int i,j, iParamTypeSeq = SYMPARMCNT + 1;
  int bIsRefereced = 0;
  giParamEntry = 0;
  if ( lookahead == '(' ){
    match('(');
    if ( lookahead == VAR ){
      bIsRefereced = 1;
      match(VAR);
    }
    {/** */
      giLexCat = PARAM;
    }/** */
    idlist();
    match(':');
    {/** */ 
      for ( i=0; i<giVarCount; i++ ){
	iSymtab[giVarPos[i]][SYMLXTYPE] = lookahead;
	giParamPos[giParamEntry++] = giVarPos[i];
	if ( bIsRefereced )
	  iSymtab[iProcFuncPos][iParamTypeSeq++] = REFERENCE;
	else
	  iSymtab[iProcFuncPos][iParamTypeSeq++] = lookahead;
	
      }
    }/** */
    type();
    while ( lookahead == ';' ){
      bIsRefereced = 0;
      match(';');
      if ( lookahead == VAR ){
        bIsRefereced = 1;
	match(VAR);
      }
      idlist();
      match(':');
      {/** */ 
        for ( i=0; i<giVarCount; i++ ){
  	  iSymtab[giVarPos[i]][SYMLXTYPE] = lookahead;
	  giParamPos[giParamEntry++] = giVarPos[i];
	  if ( bIsRefereced )
	    iSymtab[iProcFuncPos][iParamTypeSeq++] = REFERENCE;
	  else
	    iSymtab[iProcFuncPos][iParamTypeSeq++] = lookahead;
	}
      }/** */
      type();
    }
    for ( i=giParamEntry-1, j=0; i>=0; i--, j++ ){
      iSymtab[giParamPos[i]][SYMOFFSET] = (i*4) + 8;
    }
   match(')');
  }
}
int iAssertType(int iCurrType, int iSynthType){
    if ( iCurrType == REAL && iSynthType == INTEGER )
      return REAL;
    if ( iCurrType == INTEGER && iSynthType == BOOL )
      return BOOL;
    if ( iCurrType == BOOL && iSynthType == INTEGER )
      return BOOL;
  
    return iSynthType;
}
